
#include <nlohmann/json.hpp>
#include <workflow/WFTaskFactory.h>
#include <workflow/MySQLResult.h>
#include <workflow/MySQLUtil.h>
#include <wfrest/PathUtil.h>
#include <sys/stat.h>
#include <functional>
#include <iostream>
#include <alibabacloud/oss/OssClient.h>
#include <SimpleAmqpClient/SimpleAmqpClient.h>

#include "CloudiskServer.h"
#include "CryptoUtil.h"
#include "OssManage.h"

using namespace protocol;
using namespace AmqpClient;
using namespace AlibabaCloud::OSS;
using namespace std::placeholders;
using json = nlohmann::json;
using namespace wfrest;
using namespace std;

static const std::string RABBITMQ_URL = "amqp://guest:guest@localhost:5672/%2f";
static const std::string MYSQL_URL = "mysql://root:1234@localhost/workflow";

// 注册所有模块的路由
void CloudiskServer::register_modules()
{
    // 1.注册静态资源
    register_static_resources_module();
    // 2.注册用户路由
    register_signup_module();
    // 3.注册登录路由
    register_signin_module();
    // 4.注册用户信息路由
    register_userinfo_module();
    // 5.注册文件路由
    register_fileupload_module();
    // 6.注册文件列表路由
    register_filelist_module();
    // 7.注册文件下载路由
    register_filedownload_module();
}

// 注册静态资源路由
void CloudiskServer::register_static_resources_module()
{
    // 用户注册页面
    m_server.GET("/user/signup", [](const HttpReq *, HttpResp *resp)
                 { resp->File("static/view/signup.html"); });

    // 登录界面
    m_server.GET("/static/view/signin.html", [](const HttpReq *, HttpResp *resp)
                 { resp->File("static/view/signin.html"); });

    // 注册界面
    m_server.GET("/static/view/signup.html", [](const HttpReq *, HttpResp *resp)
                 { resp->File("static/view/signup.html"); });

    // 返回首页
    m_server.GET("/static/view/home.html", [](const HttpReq *, HttpResp *resp)
                 { resp->File("static/view/home.html"); });

    // javascript文件
    m_server.GET("/static/js/auth.js", [](const HttpReq *, HttpResp *resp)
                 {
                     resp->File("static/js/auth.js"); // 返回认证脚本
                 });

    // 用户头像图片
    m_server.GET("/static/img/avatar.jpeg", [](const HttpReq *, HttpResp *resp)
                 { resp->File("static/img/avatar.jpeg"); });

    // 文件上传页面
    m_server.GET("/file/upload", [](const HttpReq *, HttpResp *resp)
                 { resp->File("static/view/upload.html"); });

    // 静态目录映射：将 `/file/upload_files` 映射到本地目录 `static/view/upload_files`
    m_server.Static("/file/upload_files", "static/view/upload_files");
};

void CloudiskServer::register_signup_module()
{

    // 使用seriesHandler实现异步序列处理
    m_server.POST("/user/signup", [](const HttpReq *req, HttpResp *resp, SeriesWork *series)
                  {
        // 1.校验请求参数

        if(req->content_type()!=APPLICATION_URLENCODED){
            resp->set_status(HttpStatusBadRequest);
            resp->String("<html>400 Bad Request</html>");
        }

        // 2.解析表单参数
        auto& params=req->form_kv();    // 获取表单键值对
        string username=params["username"];
        string password=params["password"];

        cout <<username<<": "<<password<<endl;       

        // 1.检查用户是否存在
        if (username == "" || password == "")
        {
            resp->set_status(HttpStatusBadRequest);
            resp->String("<html>400 Bad Request</html>");
            return;
        }

        // 先加盐哈希，再写入数据库
        string salt=CryptoUtil::generate_salt();
        string hash_password=CryptoUtil::hash_password(password,salt);
#ifdef DEBUG
        cout << "salt: " << salt << endl;
        cout << "hash_password: " << hash_password << endl;
#endif

        // 将用户信息写入数据库
        string url("mysql://root:1234@localhost:3306/workflow");
        auto mysqlTask=WFTaskFactory::create_mysql_task(url,3,nullptr);
        string query ="insert into tbl_user(username,password,salt) values('" + username + "','" + hash_password + "','" + salt + "');";
#ifdef DEBUG
        cout << "query: " << query << endl;
#endif
        mysqlTask->get_req()->set_query(query);
        series->push_back(mysqlTask);
        // 3.返回响应
        resp->String("SUCCESS"); });
}

// 注册登录路由
void CloudiskServer::register_signin_module()
{
    m_server.POST("/user/signin", [](const HttpReq *req, HttpResp *resp, SeriesWork *series)
                  {
        // 1.校验请求参数

        if (req->content_type() != APPLICATION_URLENCODED)
        {
            resp->set_status(HttpStatusBadRequest);
            resp->String("<html>400 Bad Request</html>");
        }

        // 2.解析表单参数
        auto &params = req->form_kv(); // 获取表单键值对
        string username = params["username"];
        string password = params["password"];

        cout << username << ": " << password << endl;

        // 业务逻辑
        // 1.检查用户是否存在
        
        // 将用户信息写入数据库
        string url = "mysql://root:1234@localhost/workflow";

        auto mysqlTask = WFTaskFactory::create_mysql_task(url, 1, [resp, password, username, url, series](WFMySQLTask *task)
        {
            // 对Mysql任务进行判断和处理
            using namespace protocol;
            MySQLResultCursor cursor(task->get_resp()); // 用cursor下标来查询

            // 读取所有查询结果并存放在vector容器
            // 二维vector容器，用来存放查询结果
            vector<vector<protocol::MySQLCell>> record; //

            if (cursor.fetch_all(record))
            { // 确保第一行第一列是字符串
                // 提取第一行数据
                const auto &row = record[0];
                User user;
                user.id = row[0].as_int();             // ID
                user.username = row[1].as_string();    // 用户名
                user.hashcode = row[2].as_string();    // 哈希密码
                user.salt = row[3].as_string();        // 盐值
                user.createdAt = row[4].as_datetime(); // 创建时间
#ifdef DEBUG
                cout << "[INFO] id: " << user.id
                     << ", username: " << user.username
                     << ", hashcode: " << user.hashcode
                     << ", salt: " << user.salt << endl;
#endif
                // 使用数据库中的盐重新哈希输入密码
                string hashcode1 = CryptoUtil::hash_password(password, user.salt);
#ifdef DEBUG
                cout << "Generated hashcode: " << hashcode1 << endl;
#endif
                // 对比加密后密码
                if (hashcode1 == user.hashcode)
                {
                    // 登录成功，生成Token
                    string token = CryptoUtil::generate_token(user);

                    // 构造json，返回客户端
                    json respMsg;
                    json data;

                    data["Token"] = token;
                    data["Username"] = username;
                    data["Location"] = "/static/view/home.html";
                    respMsg["data"] = data;

                    resp->String(respMsg.dump());
                    cout << "send resp success" << endl;
                }
                else
                {
                    // 密码不匹配，则错误
                    cout << "password error" << endl;
                    resp->String("password error!");
                }
            }
            else
            {
                // 数据库返回非字符串，则服务器内部错误
                cout << "server 500 error" << endl;
                resp->set_status_code("500");
                resp->set_reason_phrase("Internal Server Error");
            }
#ifdef DEBUG
            cout << record.size() << " rows in set" << endl;
#endif
        });
        string sql = "SELECT * from tbl_user WHERE username='" + username + "'";
        mysqlTask->get_req()->set_query(sql);
        series->push_back(mysqlTask); });
}

// 用户信息管理
void CloudiskServer::register_userinfo_module()
{
    m_server.GET("/user/info", [](const HttpReq *req, HttpResp *resp, SeriesWork *series)
                 {
        // 1.解析请求参数
        User user;
        user.username=req->query("username");
        string token=req->query("token");
#ifdef DEBUG
        cout << "username: " << user.username << endl;
        cout << "token: " << token << endl;
#endif
        // 2.校验参数
        if(user.username.empty() || token.empty())
        {
            resp->String("username or token is empty");
            return;
        }

        // 3.校验token
        if(!CryptoUtil::verify_token(token,user))
        {
            resp->String("token is invalid");
            return;
        }

        // 3.查询MySQL获取注册时间
        string url="mysql://root:1234@localhost/workflow";
        string sql="SELECT created_at FROM tbl_user WHERE username='"+user.username+"'";
        cout << "sql: " << sql << endl;

        using namespace protocol;
        resp->MySQL(url,sql,[resp,user](MySQLResultCursor *pcursor){
            vector<vector<MySQLCell>> record;
            pcursor->fetch_all(record);
            if(record[0][0].is_datetime()){
                // 构造json
                json respMsg;
                json data;
                data["Username"]=user.username;
                data["SignupAt"]=record[0][0].as_datetime();
                respMsg["data"]=data;
#ifdef DEBUG
                cout << "respMsg: " << respMsg.dump() << endl;
#endif
                resp->String(respMsg.dump());
                cout <<"send userinfo success"<<endl;
            }else{
                resp->String("user not found");
            }
        }); });
}

// 文件上传
void CloudiskServer::register_fileupload_module()
{
    m_server.POST("/file/upload", [](const HttpReq *req, HttpResp *resp, SeriesWork *series)
                  {
            // 1.解析请求参数
            string username=req->query("username");
            string token=req->query("token");
            // 2.校验token
            User user;
            if(!CryptoUtil::verify_token(token,user)){
                resp->set_status(HttpStatusUnauthorized);
                resp->String("<html>401 Unauthorized</html>");
                return;
            }
            
            // 3.处理文件
            if(req->content_type()!=MULTIPART_FORM_DATA){
                resp->set_status(HttpStatusBadRequest);
                resp->String("<html>400 Bad Request</html>");
                return;
            }

            // form是一个map，key是文件名，value是文件内容
            // using Form = std::map<std::string, std::pair<std::string, std::string>>
            auto &form = req->form();
            for (const auto &[_, file] : form)  // 使用const auto& 确保不拷贝大文件内容
            {
                // 忽略key部分-表单字段名，用file取出后半段的value==pair部分
                // 再结构化绑定，取得文件名和内容，用内容来进行哈希
                // 获取文件名和文件内容
                const auto&[filename,content]=file;
                string hashcode=CryptoUtil::hash_file(content);

                // 为用户创建单独文件夹，基于用户名
                string directory="files/"+user.username+"/";
                // 如果文件夹不存在，则创建
                if(access(directory.c_str(),F_OK)){
                    mkdir(directory.c_str(),0777);
                }

                // 构造完整路径
                string filepath = directory + PathUtil::base(filename);
                cout << "filepath: " << filepath << endl;
                // 打开文件
                int fd = open(filepath.c_str(), O_RDWR | O_CREAT|O_TRUNC, 0666);
                if (fd < 0)
                {
                    resp->set_status(500);
                    resp->String("<html>500 Internal Server Error</html>");
                    return;
                }

                // 写入文件
                int ret = write(fd, content.c_str(), content.size());
                if (ret < 0)
                {
                    perror("write file failed");
                    resp->String("write file failed");
                    return;
                }
                // 关闭文件
                close(fd);

                // OSS备份
                // 将文件上传信息异步写入消息队列-交给异步消费者做备份
                Channel::ptr_t channel=Channel::CreateFromUri(RABBITMQ_URL);
                
                // 构建json消息
                nlohmann::json obj;
                obj["object"]=filepath; // OSS 对象名
                obj["file"]=filepath;   //本地文件路径
                BasicMessage::ptr_t message=BasicMessage::Create(obj.dump());

                // 发布消息到MQ
                string exchange="oss.direct";
                string routingKey="oss";
                channel->BasicPublish(exchange,routingKey,message);
                /*
                
                OssManage* oss= OssManage::get_instance();
                shared_ptr<iostream> stream=make_shared<std::stringstream>(std::move(content));
                // 将路径名作为OSS对象的名字
                if(!oss->upload_object("xia-cpp",filepath,stream)){
                    resp->String("upload file to oss failed");
                    return;
                }*/

                // 文件已完成上传，通过RabbitMQ传递消息，通知

                // 构造SQL语句-插入用户信息tbl_file
                string sql = "REPLACE INTO tbl_file (uid,filename,hashcode,size,status) VALUES("
                + std::to_string(user.id) + ",'" 
                + filename + "','" 
                + hashcode + "','" 
                + std::to_string(content.size()) + "',0)";
                cout << "sql: " << sql << endl;

                // 执行SQL语句
                resp->MySQL(MYSQL_URL, sql, [resp](MySQLResultCursor *cursor)
                            {
                if (cursor->get_cursor_status() != MYSQL_STATUS_OK) {
                    resp->set_status(HttpStatusInternalServerError);
                    resp->String("<html>500 Internal Server Error");
                    return ;
                }
                resp->Redirect("/static/view/home.html", HttpStatusSeeOther); });
           } });
}

void filelist_callback(wfrest::HttpResp *resp, protocol::MySQLResultCursor *cursor)
{
    /* MySQL任务失败 */
    if (cursor->get_cursor_status() != MYSQL_STATUS_GET_RESULT)
    {
        resp->set_status(HttpStatusInternalServerError);
        resp->String("<html>500 Internal Server Error");
        return;
    }

    /* 任务执行成功 */
    using namespace protocol;
    using Json = nlohmann::json;

    std::vector<MySQLCell> record{};

    Json result = Json::array();
    while (cursor->fetch_row(record))
    {
        Json file{};
        file["FileName"] = record[0].as_string();
        file["FileHash"] = record[1].as_string();
        file["FileSize"] = record[2].as_ulonglong();
        file["UploadAt"] = record[3].as_datetime();
        file["LastUpdated"] = record[4].as_datetime();

        result.push_back(std::move(file));
    }
    resp->String(result.dump(2));
}

void CloudiskServer::register_filelist_module()
{
    m_server.POST("/file/query", [](const HttpReq *req, HttpResp *resp)
                  {
        // 1. 解析请求
        string username = req->query("username");    
        string token = req->query("token");
        string limit = req->form_kv()["limit"];

        // 2. 校验请求参数 (这里只是简单输出)
#ifdef DEBUG
        std::cout << "username: " << username
            << ", token: " << token
            << ", limit: " << limit << "\n";
#endif
        // 3. 校验Token
        User user{};
        if (!CryptoUtil::verify_token(token, user)) {
            resp->set_status(HttpStatusUnauthorized);
            resp->String("<html>401 Unauthorized</html>");
            return ;
        }
        // 4. 构建SQL语句，查询数据库
        string sql = "SELECT filename, hashcode, size, created_at, last_update FROM tbl_file WHERE uid="
            + std::to_string(user.id)
            + " LIMIT " + limit;

        std::cout << "[SQL] " << sql << "\n";

        std::function<void(MySQLResultCursor*)> callable = std::bind(filelist_callback, resp, _1);
        resp->MySQL(MYSQL_URL, sql, callable); });
}

// 下载文件
void CloudiskServer::register_filedownload_module()
{
    m_server.GET("/file/download", [](const HttpReq *req, HttpResp *resp)
                 {
       // 1. 获取请求参数
       const string& filename = req->query("filename");
       const string& filehash = req->query("filehash");
       const string& username = req->query("username");
       const string& token = req->query("token");
        // 2. 校验请求参数 (这里只是打印)
#ifdef DEBUG
        std::cout << "filename: " << filename
            << ", filehash: " << filehash
            << ", username: " << username
            << ", token: " << token << "\n";
#endif
        User user;
        if (!CryptoUtil::verify_token(token, user)) {
            resp->set_status(HttpStatusUnauthorized);
            resp->String("<html>401 Unauthorized</html>");
            return ;
        }

        string filepath = "files/" + user.username + "/" + PathUtil::base(filename);
#ifdef DEBUG
        std::cout << "filepath: " << filepath << "\n";
#endif
        resp->set_header_pair("Content-Disposition", "attachment; filename=" + filename);
        resp->File(filepath); });
}
